<?php
namespace my\bq\criterion;
use my\bq\common\Configuration;
use my\bq\common\Log;
use my\bq\dao\PdoManager;
class CriteriaImpl implements Criteria{

	private $pdo;

	private $criterionEntries =array();
	private $projectionEntries = array();
	private $joinEntries = array();
	private $orderEntries = array();
	private $groupEntries = array();
	private $relationEntries = array();

	private $criterias = array();
	private $table = "";
	private $tableEntity = "";
	private $alias = "";
	private $fileds = array();
	private $columns = array(); 

	const  ROOT_ALIAS = "this_";
	private $firstResult = 0;
	private $fetchSize = 0;

	private $fullQuery = "";
	private $fullGroupQuery = "";
	private $fullOrderQuery = "";
	private $fullJoinQuery = "";
	private $fullProjectionQuery = "";
	private $parameters = array();

	private $sqlString;

	function __construct($entityClass = null){
		$entityConf = $entityClass->getConfig();
		$this->table = $entityConf['name'];
		$this->columns = $entityConf['columns'];
		$this->tableEntity = $entityClass;
		$this->fileds =  $entityClass->getFileds();
		$entityClass->setCriteria($this);
		
		//interator
		if(isset($entityConf['relations'])&& is_array($entityConf['relations'])){
			foreach ($entityConf['relations'] as $k => $relation){
				// = current($relationItem);
				$rEntity = $relation['class'];
				if(is_object($rEntity)){
					$crit = $this->createCriteria($rEntity);
					$cache = CacheManager::getInstance();
					$cacheTabEntity = $cache->get('tableEntity');
					if(!isset($cacheTabEntity)){
						$cacheTabEntity = array();
					}
					
					$cacheTabEntity[$entityConf['alias']] = $rEntity;
					$cache->set('tableEntity',$cacheTabEntity);
				}
			}
		}
	}

	public function add($T,$criterion){
		array_push($this->criterionEntries,array($T,$criterion));
		return $this;
	}
	public function addOrder($order){
		array_push($this->orderEntries,$order);
		return $this;
	}
	public function addGroup($group){
		array_push($this->groupEntries, $group);
		return $this;
	}

	public function addProjection($projection){
		array_push($this->projectionEntries, $projection);
		return $this;
	}

	public function addJoin($join){
		array_push($this->joinEntries, $join);

		$cache = CacheManager::getInstance();
		$join_table = $cache->get('join_table');
		if(!$join_table)$join_table = array();
		array_push($join_table, $join->getJoinCriteria()->getAlias());
		$cache->set('join_table',$join_table);
		return $this;
	}

	public function getJoinEntries(){
		return $this->joinEntries;
	}

	public function addRelation($relation){
		array_push($this->relationEntries,$relation);
	}

	public function getRelation(){
		return  $this->relationEntries;
	}

	public function getOrderEntries(){
		return $this->orderEntries;
	}

	public function getGroupEntries(){
		return $this->groupEntries;
	}


	private function build(){
		$query = "";
		foreach ($this->criterionEntries as $criterion){
			$singleUseingQuery = $criterion[1]->toSqlString($this);
			$query.= ($query || $this->getAlias() != self::ROOT_ALIAS)?($criterion[0].$singleUseingQuery):$singleUseingQuery;
		}
		$this->fullQuery = $query;
		if($this->criterias)
			foreach ($this->criterias as $criteria){
			$this->fullQuery .= $criteria->getQuery();
		}
	}

	public function buildOrderQuery(){

		$orderQuery = "";
		//get current orderEntries;
		$orderEntries = $this->getOrderEntries();
		foreach ($orderEntries as $order){
			$singleUseingQuery = $order->toSqlString($this);
			$orderQuery.= ($orderQuery || $this->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
		}
		unset($orderEntries);
		unset($singleUseingQuery);
		unset($order);

		//get orderEntries from criterias;
		foreach ($this->criterias as $criteria) {
			$criterias = $criteria->getCriterias();
			foreach ($criterias as $cri){
				$orderEntries = $cri->getOrderEntries();
				foreach ($orderEntries as $order){
					$singleUseingQuery = $order->toSqlString($cri);
					$orderQuery.= ($orderQuery || $cri->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
				}
			}
			unset($orderEntries);
			unset($singleUseingQuery);


			$orderEntries = $criteria->getOrderEntries();
			foreach ($orderEntries as $order){
				$singleUseingQuery = $order->toSqlString($criteria);
				$orderQuery.= ($orderQuery || $criteria->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
			}
		}
		$this->fullOrderQuery .= $orderQuery;

	}

	public function buildJoinQuery(){

		$joinQuery = "";
		//get current orderEntries;
		$joinEntries = $this->getJoinEntries();
		foreach ($joinEntries as $join){
			$singleUseingQuery = $join->toSqlString($this);
			$joinQuery.= $singleUseingQuery;
		}
		unset($joinEntries);
		unset($singleUseingQuery);
		unset($join);

		//get orderEntries from criterias;
		foreach ($this->criterias as $criteria) {
			$criterias = $criteria->getCriterias();
			foreach ($criterias as $cri){
				$joinEntries = $cri->getJoinEntries();
				foreach ($joinEntries as $join){
					$singleUseingQuery = $join->toSqlString($cri);
					$joinQuery.= $singleUseingQuery;
				}
			}
			unset($orderEntries);
			unset($singleUseingQuery);


			$joinEntries = $criteria->getjoinEntries();
			foreach ($joinEntries as $join){
				$singleUseingQuery = $join->toSqlString($criteria);
				$joinQuery.= $singleUseingQuery;
			}
		}
		$this->fullJoinQuery .= $joinQuery;
	}

	public function buildGroupQuery(){

		$groupQuery = "";
		$groupEntries = $this->getGroupEntries();
		foreach ($groupEntries as $group){
			$singleUseingQuery = $group->toSqlString($this);
			$groupQuery.= ($groupQuery || $this->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
		}
		unset($groupEntries);
		unset($singleUseingQuery);

		//get orderEntries from criterias;
		foreach ($this->criterias as $criteria) {
			$criterias = $criteria->getCriterias();
			foreach ($criterias as $cri){
				$groupEntries = $cri->getGroupEntries();
				foreach ($groupEntries as $group){
					$singleUseingQuery = $group->toSqlString($cri);
					$groupQuery.= ($groupQuery || $cri->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
				}
			}
			unset($groupEntries);
			unset($singleUseingQuery);

			$groupEntries = $criteria->getGroupEntries();
			foreach ($groupEntries as $group){
				$singleUseingQuery = $group->toSqlString($criteria);
				$groupQuery.= ($groupQuery || $criteria->getAlias() != self::ROOT_ALIAS)?(', '.$singleUseingQuery):$singleUseingQuery;
			}
		}
		$this->fullGroupQuery .= $groupQuery;
	}

	private function buildProjectionQuery(){
		$query = "";
		foreach ($this->projectionEntries as $projection){
			$singleUseingQuery = $projection->toSqlString($this);
			$query.= ($query || $this->getAlias() != self::ROOT_ALIAS)?(", ".$singleUseingQuery):$singleUseingQuery;
		}
		$this->fullProjectionQuery = $query;
		if($this->criterias)
			foreach ($this->criterias as $criteria){
			$this->fullProjectionQuery .= $criteria->getProjectionQuery();
		}
	}

	public function setExample($example){
		foreach ($example as $k => $v){
			$this->add(Criteria::_AND, Restrictions::eq($k, $v));
		}
		return $this;
	}

	public function setFetchSize($fetchSize){
		$this->fetchSize = $fetchSize;
		return $this;
	}
	public function setFirstResult($firstResult){
		$this->firstResult = $firstResult;
		return $this;
	}

	public function getQuery(){
		$this->build();
		return $this->fullQuery;
	}

	public function getOrderQuery(){
		$this->buildOrderQuery();
		return $this->fullOrderQuery;
	}

	public function getGrounpQuery(){
		$this->buildGroupQuery();
		return $this->fullGroupQuery;
	}

	public function getProjectionQuery(){
		$this->buildProjectionQuery();
		return $this->fullProjectionQuery;
	}

	public function getJoinQuery(){
		$this->buildJoinQuery();
		return $this->fullJoinQuery;
	}


	public function addCriteria($criteria){
		array_push($this->criterias, $criteria);
		return $this;
	}

	public function createCriteria($entityClass){
		
		$criteria = new CriteriaImpl($entityClass);
		$entity_confg = $entityClass->getConfig();
		$criteria->alias = $entity_confg['alias'];
		array_push($this->criterias, $criteria);
		return $criteria;
	}

	public function getCriterias(){
		return $this->criterias;
	}

	public function getAlias(){
		return $this->alias?$this->alias:self::ROOT_ALIAS;
	}

	public function getTable(){
		return $this->table;
	}

	public function setFileds($fileds){
		$this->fileds = (is_string($fileds)||is_object($fileds))?array($fileds):$fileds;
		return $this;
	}
	
	public function setColumns($columns){
		$this->columns = $columns;
		return $this;
	}

	public function getFileds(){
		return $this->fileds;
	}

	public function getSelectQuery($isSubQuery = false,$isAppend = false){

		$selectQuery = "";
		$projectionQuery = null;
		$aheadString="";
		if(!$isSubQuery){
			$projectionQuery = $this->getProjectionQuery();
			$aheadString = $projectionQuery?("select ".$projectionQuery." "):"select ";
		}

		$cache = CacheManager::getInstance();
		$join_table = $cache->get('join_table');
		if($this->fileds && ($this->getAlias() == self::ROOT_ALIAS ||(is_array($join_table) && in_array($this->alias, $join_table)))){
			
			if($this->fileds[0] == '*') $this->fileds = $this->columns;
			foreach ($this->fileds as $filed){
				
				$selectQuery .= ((($selectQuery || ($isSubQuery && $isAppend)) ?', ':$aheadString).(($projectionQuery && !$isSubQuery)?', ':'').$this->getAlias().'.');
				if(is_object($filed)){
					$selectQuery .= ($filed->getPropertyName().' as '.$this->getAlias().'__'.trim($filed->getAlias(),'`'));
				}else{
					$selectQuery .= $filed.' as '.$this->getAlias().'__'.trim($filed,'`');
				}
			}
		}else{
			$selectQuery .= $aheadString;
			//$selectQuery = ($isSubQuery)?", ".$this->getAlias().".*": $aheadString.$this->getAlias().".* ";
		}

		if($this->criterias)
			foreach ($this->criterias as $criteria){
			$selectQuery .= ((($selectQuery || $isSubQuery)?'':$aheadString).$criteria->getSelectQuery(true,($projectionQuery||$this->fileds)));
		}

		return $selectQuery;
	}

	public function createProperty($property){
		return new property($property,$this->getAlias());
	}

	//clean query;
	public function cleanProjection(){
		$this->projectionEntries = array();
		$this->fullProjectionQuery = "";
		$this->fullJoinQuery = "";
		$this->fullGroupQuery = "";
		$this->fullOrderQuery = "";
		$this->fullQuery = "";
		$this->fileds = array();
		$this->sqlString = "";
		CriteriaQuery::$parameters = array();
	}

	public function cleanQuery(){
		$this->criterionEntries = array();
		$this->fullJoinQuery = "";
		$this->fullGroupQuery = "";
		$this->fullOrderQuery = "";
		$this->fullQuery = "";
		$this->sqlString = "";
	}

	public function fetchRelationData($tableEntity,$config){
		$entityCfg =  $tableEntity->getConfig();
		$key = $tableEntity->$config['key'];
		$fileds = $config['class']->getFileds(); 
		$relateCfg =  $config['class']->getConfig();
		if($fileds[0] == '*'){
			$fileds = $relateCfg['columns'];
		}
		
		$sql = "select ".implode(",", $fileds)." from ".$relateCfg['name']." where ".$config['column'];
		
		//cache
		$cache = CacheManager::getInstance();
		if($data = $cache->get($sql." = ".$key)){
			return $data;
		}else{
			//write sql;
			if(Configuration::SHOW_SQL)Log::writeMsg(Log::NOTICE, $sql." = ".$key);
			
			$pdoManager = PdoManager::getInstance();
			$pdo = $pdoManager->getPdo($tableEntity);
			
			$rs = $pdo->getRows($sql." = ?",array($key));
			
			if($rs){
				
				$class = get_class($config['class']);
				if($config['relation'] == 'one-to-one'){
					$rs = current($rs);
					$data = new $class();
					foreach($rs as $k => $v){
						$data->$k = $v;
					}
					//检查多级关联
					$cfgl = $data->getRelationPro();
					if(isset($cfgl) && is_array($cfgl)){
						foreach ($cfgl as $pro =>$cfg){
							if(!isset($cfg['lazy']) || !$cfg['lazy'] == true){
								$data->$cfg['name'] = $this->fetchRelationData($data,$cfg);
							}
						}
					}
					
					$data->setCriteria($this);
					$cache->set($sql." = ".$key,$data);
					return $data;
										
				}else{
					$dataList = array();
					foreach ($rs as $i => $item){
						$data = new $class();
						foreach($item as $k => $v){
							$data->$k = $v;
						}
						$data->setCriteria($this);
						
						//检查多级关联
						$cfgl = $data->getRelationPro();
						if(isset($cfgl) && is_array($cfgl)){
							foreach ($cfgl as $pro =>$cfg){
								if(!isset($cfg['lazy']) || !$cfg['lazy'] == true){
									$data->$cfg['name'] = $this->fetchRelationData($data,$cfg);
								}
							}
						}
						array_push($dataList, $data);						
					}
					
					$cache->set($sql." = ".$key,$dataList);
					return $dataList;
				}
			}
		}
		
		return null;
	}
	
	// to object
	public function dataToObject($tableEntitys,$data){
		
		if($data && isset($tableEntitys) && is_array($tableEntitys)){
			$tableEntity = null;
			$n = 0;
			foreach ($data as $k =>$v){
				$_f = explode('__', $k);
				if($n==0){
					$class = null;
					if($_f[0] == 'this'){
						$class = get_class($this->tableEntity);
					}else{
						$class = get_class($tableEntitys[$_f[0]]);
					}
					if($class){
						$tableEntity = new $class();
					}
					  
				}
				$p = trim($_f[1],'_');
				if($_f[0] == 'this'){
					$tableEntity->$p = $v;
				}else{
					$tableEntity->fullSet(array($p=>$v));
				}
				
				$tableEntity->setCriteria($this);
				$n++;
			}
			
			//get relation data
			$relationCfg  = $tableEntity->getRelationPro();
			foreach ($relationCfg as $var => $conf){
				if(!isset($conf['lazy']) || !$conf['lazy'] == true){
					$tableEntity->$var = $this->fetchRelationData($tableEntity,$conf);
				}
			}
			
			return $tableEntity;
		}
		return null;
	}

	public function sql(&$returnParameters = ""){
		if($this->sqlString) return $this->sqlString;

		$query  = $this->getQuery();
		$groupQuery = $this->getGrounpQuery();
		$orderQuery = $this->getOrderQuery();
		$joinQuery = $this->getJoinQuery();

		$sql = $this->getSelectQuery();
		$sql .= ' from '.$this->table.' as '.$this->getAlias();

		if($joinQuery)
			$sql .= $this->fullJoinQuery;

		if($query)
			$sql.=' where '.$query;

		if($groupQuery){
			if(!$query) $sql.= ' where true ';
			$sql.= ' group by ' . $groupQuery;
		}
		if($orderQuery){
			if(!$query && !$groupQuery) $sql.= ' where true ';
			$sql.= ' order by ' . $orderQuery;
		}
		if($this->fetchSize > 0){
			$sql.= ' limit ' . $this->firstResult.','.$this->fetchSize;
		}

		$this->parameters = CriteriaQuery::$parameters;
		CriteriaQuery::$parameters = array();
		$returnParameters = $this->parameters;
		$this->sqlString=$sql;
		//write sql;
		if(Configuration::SHOW_SQL)Log::writeMsg(Log::NOTICE, CriteriaQuery::getDumpSQL($sql, $returnParameters));
		return ($this->sqlString);
	}

	public function getParameters(){
		return $this->parameters;
	}

	public function setPdo($pdo){
		$this->pdo = $pdo;
		return $this;
	}



	/**
	 * set Data Translater
	 * @param DataTranslater $dataTranslater
	 *
	 */
	public function setDataTranslater($dataTranslater){
		$dataTranslater->setCriteria($this);
		$cache = CacheManager::getInstance();
		$transData = $cache->get('trans_data');
		if(!$transData)
			$transData = array();

		array_push($transData, $dataTranslater);

		$cache->set('trans_data',$transData);
		return $this;
	}


	//需要PDO实现 getRows方法;
	public function _array(){
		
		$pdoManager = PdoManager::getInstance();
		$pdo = $pdoManager->getPdo($this->tableEntity);
		
		$dataArray = $pdo->getRows($this->sql(),$this->getParameters());

		$cache = CacheManager::getInstance();
		$transData = $cache->get('trans_data');
		$tableEntitys = $cache->get('tableEntity');
		if(!$tableEntitys)$tableEntitys = array($this->tableEntity);
		
		if(Relation::$hasRelation || $transData || $tableEntitys){
			if($transData){
				foreach ($dataArray as $k =>$data){
					foreach($transData as $crit => $dataTranslater){
						$property = $dataTranslater->getProperty();
						if(!isset($data[$property->getPropertyName()])) break;
						$value = $data[$property->getPropertyName()];
						if($value){
							$data[$property->getAlias()] = $dataTranslater->translate($value);
							$dataArray[$k] = $data;
						}
					}
				}
			}
			
			if($tableEntitys){
				foreach ($dataArray as $k =>$data){
					//if(Relation::$hasRelation) //has relation
					//to object
					$dataArray[$k] = $this->dataToObject($tableEntitys,$data);
				}
			}
		}
		Relation::$hasRelation = false;
		CacheManager::getInstance()->clean();
		return $dataArray;
	}

}